#Requires -version 2.0
#Requires -PSSnapIn Quest.ActiveRoles.ADManagement

function Get-ComponentModuleDetail
{
  <#
    .SYNOPSIS
      Gathers details about a running process' component module.
    .DESCRIPTION
      Creates a comma separated value list of details about a specified running process' component module.  By default the module's name,size,filename, and fileversion are written to output.  This information can be used in the console, consumed in another pipeline, or redirected to file using Out-File or the >/>> redirection syntax.
    .PARAMETER ComputerName
      A single computer name or array of computer names.  Can be piped into the function.  Defaults to the localhost.
    .PARAMETER GetComputersFromAD
      Instructs the function to gather all DNSNames from Active Directory.  Assumes that you have the necessary permissions to gather the information, or have already connected to AD using the Connect-QADService cmdlet.  If any names are specified in the ComputerName parameter they will be overridden.
    .PARAMETER ProcessName
      The name of the process to launch and inspect for module details.  Defaults to $env:systemroot\Notepad.exe.
    .PARAMETER ModuleDescription
      A string to match against the description of the Module to gether details about from the launched ProcessName.  Defaults to 'Windows Spooler Driver'
    .PARAMETER Property
      A single property or list of properties to gather from the module that matches the module description.  Defaults to 'ModuleName','Size','FileName','FileVersion'
    .PARAMETER RemotingCredential
      If you require specifying alternate credentials for PSRemoting, supply them in one fashion or another from Get-Credential.
    .EXAMPLE
      PS C:\> Get-ComponentModuleDetail
      "ComputerName","ModuleName","Size","FileName","FileVersion"
      "localhost","WINSPOOL.DRV","452","C:\Windows\system32\WINSPOOL.DRV","6.1.7600.16385 (win7_rtm.090713-1255)"
      
      Gathers the default information and displays it in the console
    .EXAMPLE
      PS C:\> Get-ComponentModuleDetail -GetComputersFromAD > C:\Event1_Advanced.csv
      
      Gathers the default information from computers that are accessible on the network and writes the results to a .csv file
    .EXAMPLE
      PS C:\> Get-ComponentModuleDetail -ProcessName $env:systemroot\system32\calc.exe -ModuleDescription 'GDI\+' -Property Company,Description,Product
      "ComputerName","Company","Description","Product"
      "localhost","Microsoft Corporation","Microsoft GDI+","Microsoftr Windowsr Operating System"
      
      Gathers various details about modules loaded by calc.exe that match the regex GDI\+ from the local system
    .EXAMPLE
      'WS1','SRV1','SRV2' | Get-ComponentModuleDetail -RemotingCredential (Get-Credential)
      
      Gathers the default information from the computernames piped into the function.  The remote machines will be connected to using the credentials specified to the Get-Credential cmdlet.
  #>
  param
  (
    [CmdletBinding()]
    [parameter(
      Mandatory=$false,
      Position=0,
      ValueFromPipeline=$true,
      HelpMessage="A single name, or an array of computer names."
    )]
    [ValidateNotNullOrEmpty()]
    [String[]]$ComputerName = ".",

    [parameter(
      Mandatory=$false,
      Position=0,
      HelpMessage="Gathers all DNS names from computer accounts in Active Directory.  Assumes you have the necessary permissions to access this information."
    )]
    [switch]$GetComputersFromAD,

    [parameter(
      Mandatory=$false,
      Position=1,
      HelpMessage="The full path of the process to launch and gather module information from."
    )]
    [ValidateNotNullOrEmpty()]
    [String]$ProcessName = "$env:systemroot\notepad.exe",
    
    [parameter(
      Mandatory=$false,
      Position=2,
      HelpMessage="A regular expression to match against the description of the loaded modules of `$ProcessName"
    )]
    [ValidateNotNullOrEmpty()]
    [String]$ModuleDescription = 'Windows Spooler Driver',
    
    [parameter(
      Mandatory=$false,
      Position=3,
      HelpMessage="A list of properties to gather from the matching module(s)"
    )]
    [ValidateNotNullOrEmpty()]
    [String[]]$Property = @('ModuleName','Size','FileName','FileVersion'),
    
    [parameter(
       Mandatory=$false,
       Position=4,
       HelpMessage="PSCredentials to be used to connect to remote machines. Use Get-Credential or a previously created PSCredential variable."
    )]
    [ValidateNotNullOrEmpty()]
    [System.Management.Automation.PSCredential]$RemotingCredential
  )
  
  # If we are gathering computers from AD, populate the computername variable with all the DNSNames of accounts in AD
  if ($GetComputersFromAD)
  {
    $ComputerName = @(Get-QADComputer -SizeLimit 0 -ShowProgress | Select-Object -ExpandProperty DNSName)
  }
  
  # Scriptblock to be run against all machines in $ComputerName
  $sb = {
    param($ProcessName,$ModuleDescription)
    # Ensure $ProcessName exists
    if (Test-Path $ProcessName)
    {
      # Start the process hidden and give it a second to initialize
      $ps = Start-Process $ProcessName -PassThru -WindowStyle Hidden
      Start-Sleep 1
      # Write the modules that match the description to output
      $ps.modules | Where-Object { $_.description -match $ModuleDescription }
      # Kill our Process before exiting the scriptblock
      Stop-Process $ps.id
    }
  }
  
  # Fan-out remoting to gather information as quickly as possible.  This can result in lots of red if many machines are not available, but we only
  # have 200 or so and 4 servers, so it should be managable.  Also can use $ErrorActionPreference to "quiet" the function down.
  if ($RemotingCredential -ne $null)
  {
    $results = @(Invoke-Command -ComputerName $ComputerName -ScriptBlock $sb -Credential $RemotingCredential -ArgumentList $ProcessName,$ModuleDescription)
  }
  else
  {
    $results = @(Invoke-Command -ComputerName $ComputerName -ScriptBlock $sb -ArgumentList $ProcessName,$ModuleDescription)
  }
  
  # Construct the CSV Header from ComputerName and the properties specified
  $outHeader = '"'
  $outHeader += 'ComputerName","'
  $outHeader += $Property -join '","'
  $outHeader += '"'
  # Write the header to output
  $outHeader
  
  # Construct a CSV line for each result from our remoting process
  foreach ($r in $results)
  {
    $outStr = '"'
    $outStr += '{0}","' -f $r.PSComputerName
    $outStr += ($Property | ForEach-Object `
    {
      # If any property does not exist for one reason or another, we want an empty field in the CSV - and not data inconsistent with the headers
      try
      {
        Select-Object -ExpandProperty $_ -InputObject $r -ErrorAction Stop
      }
      catch
      {
        ''
      }
    }) -join '","'
    $outStr += '"'
    # Write the line to output
    $outStr
  }
}

# Demonstrate functionality if script is just simply run
Get-ComponentModuleDetail